/*
 * Copyright (c) 2009 by unreserved.org.
 * All rights reserved.
 */

package org.unreserved.ladderplot;

import java.io.*;
import java.util.regex.*;
import org.apache.log4j.Logger;
import org.unreserved.util.*;

/**
 * Reads a ".lap" file and constructs in-memory version of it.
 */
public class LapReader {
    private static final Logger logger = Logger.getLogger(LapReader.class);
    private static final String NL = System.getProperty("line.separator", "\n");
    
    // opt/columnPitch/20
    private static final String OPTION       = "opt/(\\w+)/(\\w+)";
    // guy/b/Proxy 1
    private static final String PLAYER_DEF   = "guy/(\\w+)/(.*)";
    // note/b/look up profile
    private static final String NOTE         = "note/([\\^]|\\w+)/(.+)";
    // a->b/INVITE
    private static final String MESSAGE      = "(\\w+)->(\\w+)/(.*)";
    // a..b/RTP
    private static final String MEDIA        = "(\\w+)\\.\\.(\\w+)/(.+)";
    // attr/alignment/left
    private static final String ATTR         = "a=(\\w+):([ ]|.*)";
    private static final String HGROUP_START = "<hgroup>";
    private static final String HGROUP_END   = "</hgroup>";
    private static final String CDATA_START  = "<![CDATA[";
    private static final String CDATA_END    = "]]>";
    
    private static Pattern optionPattern = Pattern.compile(OPTION);
    private static Pattern playerPattern = Pattern.compile(PLAYER_DEF);
    private static Pattern notePattern = Pattern.compile(NOTE);
    private static Pattern messagePattern = Pattern.compile(MESSAGE);
    private static Pattern mediaPattern = Pattern.compile(MEDIA);
    private static Pattern attrPattern = Pattern.compile(ATTR);
    
    protected LineNumberReader in;
    protected Model model;
    protected HGroup hgroup;
    protected Container container;
    protected Object obj;
    
    public LapReader() {
    }
    
    public synchronized Model parse(File input)
        throws LapParseException, IOException, PropertySetterException
    {
        String line;
        FileReader fin = new FileReader(input);
        Matcher m;
        
        in = new LineNumberReader(fin);
        container = model = new Model();
        
        while ((line = in.readLine()) != null) {
            //line = line.trim();
            if (line.length() == 0 || line.charAt(0) == '#') {
                continue;
            }
            
            if ((m = matches(optionPattern, line)) != null) {
                String name = m.group(1);
                String value = m.group(2);
                PropertySetter.setProperty(model, name, value);
            } else if ((m = matches(playerPattern, line)) != null) {
                String nickname = m.group(1);
                String name = m.group(2);
                obj = model.addPlayer(name, nickname);
            } else if ((m = matches(notePattern, line)) != null) {
                String nickname = m.group(1);
                String text = m.group(2);
                Player p = getPlayer(nickname);
                Label label = new Label(text, p);
                label.setAnchorPos(XPosition.RIGHT);
                container.addEvent(label);
                obj = label;
            } else if ((m = matches(messagePattern, line)) != null) {
                Player from = getPlayer(m.group(1));
                Player to = getPlayer(m.group(2));
                Message msg = new Message(from, to, m.group(3));
                container.addEvent(msg);
                obj = msg;
            } else if ((m = matches(mediaPattern, line)) != null) {
                // just a message with certain default line properties
                Player from = getPlayer(m.group(1));
                Player to = getPlayer(m.group(2));
                Message msg = new Message(from, to, m.group(3));
                msg.setLinePattern(".");
                msg.setLtrLineEnding("");
                msg.setRtlLineEnding("");
                container.addEvent(msg);
                obj = msg;
            } else if ((m = matches(attrPattern, line)) != null) {
                String name = m.group(1);
                String value = m.group(2);
                
                if (value.startsWith(CDATA_START)) {
                    // append lines to attribute value until "]]>" seen
                    line = value.substring(CDATA_START.length());
                    value = "";
                    do {
                        int cdataEndIndex = line.indexOf(CDATA_END);
                        if (cdataEndIndex >= 0) {
                            value += line.substring(0, cdataEndIndex);
                            break;
                        } else {
                            value += line + NL;
                        }
                    } while ((line = in.readLine()) != null);
                }
                
                if (!PropertySetter.setProperty(obj, name, value)) {
                    logger.debug("failed to set property (" + name +
                                 "," + value + ") on " + obj);
                }
            } else if (line.equals(HGROUP_START)) {
                if (hgroup != null) {
                    throw new RuntimeException("nested hgroups not supported");
                }
                obj = container = hgroup = new HGroup();
            } else if (line.equals(HGROUP_END)) {
                if (hgroup == null) {
                    throw new RuntimeException("stray </hgroup>");
                }
                model.addEvent(hgroup);
                hgroup = null;
                container = model;
            } else {
                throw new LapParseException("parse error in line " + in.getLineNumber() + ": [" + line + "]");
            }
        }
        
        try {
            in.close();
            in = null;
        } catch (Exception ex) {
            logger.debug("ignoring exception", ex);
        }
        
        return model;
    }
    
    private Matcher matches(Pattern p, String s) {
        Matcher m = p.matcher(s);
        if (m.matches()) {
            return m;
        } else {
            return null;
        }
    }
    
    private Player getPlayer(String nickname) throws LapParseException {
        Player p = model.getPlayer(nickname);
        if (p == null) {
            throw new LapParseException("no player with nickname \"" + nickname + "\" (line " + in.getLineNumber() + ")");
        }
        return p;
    }
}
